﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using DotSpatial.Data;
using DotSpatial.Serialization;

using System.Windows.Forms;



//using DotSpatial.Symbology;

using DotSpatial.Topology;



using Point = System.Drawing.Point;

using DotSpatial;





namespace FXnaGis
{
   public class FMapFrame
    {


       public FMapFrame(Control inParent, Extent inExtent)
       {

           //


           this.LayerService = new LayerService(this);


           this._parent = inParent;

           

           this.ViewExtents = inExtent;

           _backBuffer = CreateBuffer();
           




       }

       #region Events

      


       /// <summary>
       /// Occurs when the buffer content has been altered and any containing maps should quick-draw
       /// from the buffer, followed by the tool drawing.
       /// 缓存图像已经发生变化
       /// </summary>
       public event EventHandler<FClipArgs> BufferChanged;

       #endregion Events


       LayerService _layerService;

       public LayerService LayerService
       {
           get { return _layerService; }
           set { _layerService = value; }
       }




       //双缓冲绘制系统


       private Image _backBuffer;

       private Image _buffer;


       //包含自己的容器控件
       Control _parent;

       public Control Parent
       {
           get { return _parent; }
           
       }

       private bool _resizing;
    
       private int _height;
       private int _width;
       private Rectangle _backView;


       /// <summary>
       /// 用于控制当前变化是否刷新视图
       /// </summary>
       protected bool _isSuspend=false;



       bool _isPanning;

       public bool IsPanning
       {
           get { return _isPanning; }
           set { _isPanning = value; }
       }




       Rectangle _view;
       /// <summary>
       /// gets or sets the rectangle in pixel coordinates that will be drawn to the entire screen.
       /// 这里得到的是屏幕坐标下的pixel
       /// </summary>
       public Rectangle View
       {
           get { return _view; }
           set { _view = value;
           

           
           }
       }


       /// <summary>
       /// Gets or sets the client rectangle
       /// 客户区坐标
       /// </summary>
       public Rectangle ClientRectangle
       {
           get { return new Rectangle(0, 0, _width, _height); }
       }



       /// <summary>
       /// Gets the geographic extents
       /// 对外的Geo区域,可视范围内
       /// </summary>
       public Extent GeographicExtents
       {
           get { return BufferToProj(View); }
       }




       protected Extent _viewExtents;


       /// <inheritdoc/>
       public  Extent ViewExtents
       {
           get
           {
               return _viewExtents;
           }
           set
           {
               if (value == null) return;
               Extent ext = value.Copy();
               ResetAspectRatio(ext);


               // reset buffer initializes with correct buffer.  Don't allow initialization yet.
               //SuspendExtentChanged();

               this._viewExtents = ext;
              
               ResetBuffer();

               //ResumeExtentChanged(); // fires the needed event.

           }
       }





       /// <summary>
       /// If a BackBuffer.Extents exists, this will enlarge those extents to match the aspect ratio
       /// of the pixel view.  If one doesn't exist, the _mapFrame.Extents will be used instead.
       /// </summary>
       /// <param name="newEnv">The envelope to adjust</param>
       protected void ResetAspectRatio(Extent newEnv)
       {
           // ---------- Aspect Ratio Handling
           if (newEnv == null) return;
           double h = Parent.Height;
           double w = Parent.Width;
           // It isn't exactly an exception, but rather just an indication not to do anything here.
           if (h == 0 || w == 0) return;

           double controlAspect = w / h;
           double envelopeAspect = newEnv.Width / newEnv.Height;
           Coordinate center = newEnv.ToEnvelope().Center();

           if (controlAspect > envelopeAspect)
           {
               // The Control is proportionally wider than the envelope to display.
               // If the envelope is proportionately wider than the control, "reveal" more width without
               // changing height If the envelope is proportionately taller than the control,
               // "hide" width without changing height
               newEnv.SetCenter(center, newEnv.Height * controlAspect, newEnv.Height);
           }
           else
           {
               // The control is proportionally taller than the content is
               // If the envelope is proportionately wider than the control,
               // "hide" the extra height without changing width
               // If the envelope is proportionately taller than the control, "reveal" more height without changing width
               newEnv.SetCenter(center, newEnv.Width, newEnv.Width / controlAspect);
           }
           _resizing = false;

           return;
       }



       /// <summary>
       /// This is not called during a resize, but rather after panning or zooming where the
       /// view is used as a guide to update the extents.  This will also call ResetBuffer.
       /// 视图区域发生明显变化后，用于更新
       /// </summary>
       public virtual void ResetExtents()
       {
           // Find the geographic extents that would be centered on the current view
           Extent env = BufferToProj(View);
           ViewExtents = env;

       }




       /// <summary>
       /// Re-creates the buffer based on the size of the control without changing
       /// the geographic extents.  This is used after a resize operation.
       /// 有控件本身大小发生变化，引起的绘图重置，
       /// 
       /// 
       /// </summary>
       public virtual void ResetBuffer()
       {

           //这里强制刷新了后背缓存

           _backBuffer = CreateBuffer();
           // reset the view rectangle to represent the same region
           //_view = _extendBuffer ? new Rectangle(_width / 3, _height / 3, _width / 3, _height / 3) : new Rectangle(0, 0, _width, _height);

           Initialize();
       }





      /// <summary>
      /// 创建后背缓存
      /// </summary>
      /// <returns></returns>
       private Bitmap CreateBuffer()
       {
           if (_parent != null)
           {
               _width = _parent.ClientSize.Width;
               _height = _parent.ClientSize.Height;
           }
           else
           {
               _width = 1000;
               _height = 800;
           }
           if (_height < 5) _height = 5;
           if (_width < 5) _width = 5;

           _backView = new Rectangle(0, 0, _width, _height);

        

           return new Bitmap(_width, _height);
       }



       /// <summary>
       /// Unlike PixelToProj, which works relative to the client control,
       /// BufferToProj takes a pixel coordinate on the buffer and
       /// converts it to geographic coordinates.
       /// </summary>
       /// <param name="position">A Point describing the pixel position on the back buffer</param>
       /// <returns>An ICoordinate describing the geographic position</returns>
       public Coordinate BufferToProj(Point position)
       {
           IEnvelope view = this.ViewExtents.ToEnvelope();
           if (this.ViewExtents == null) return new Coordinate(0, 0);
           double x = Convert.ToDouble(position.X);
           double y = Convert.ToDouble(position.Y);
           x = x * view.Width / _width + view.Minimum.X;
           y = view.Maximum.Y - y * view.Height / _height;

           return new Coordinate(x, y, 0.0);
       }

       /// <summary>
       /// This projects a rectangle relative to the buffer into and IEnvelope in geographic coordinates.
       /// </summary>
       /// <param name="rect">A Rectangle</param>
       /// <returns>An IEnvelope interface</returns>
       public Extent BufferToProj(Rectangle rect)
       {
           Point tl = new Point(rect.X, rect.Y);
           Point br = new Point(rect.Right, rect.Bottom);
           Coordinate topLeft = BufferToProj(tl);
           Coordinate bottomRight = BufferToProj(br);
           return new Extent(topLeft.X, bottomRight.Y, bottomRight.X, topLeft.Y);
       }



       /// <summary>
       /// Converts a single geographic location into the equivalent point on the
       /// screen relative to the top left corner of the map.
       /// </summary>
       /// <param name="location">The geographic position to transform</param>
       /// <returns>A Point with the new location.</returns>
       public Point ProjToBuffer(Coordinate location)
       {
           IEnvelope view = ViewExtents.ToEnvelope();
           if (_width == 0 || _height == 0) return new Point(0, 0);
           int x = Convert.ToInt32((location.X - view.Minimum.X) * (_width / view.Width));
           int y = Convert.ToInt32((view.Maximum.Y - location.Y) * (_height / view.Height));
           return new Point(x, y);
       }


       /// <summary>
       /// Converts a single geographic envelope into an equivalent Rectangle
       /// as it would be drawn on the screen.
       /// </summary>
       /// <param name="env">The geographic IEnvelope</param>
       /// <returns>A Rectangle</returns>
       public Rectangle ProjToBuffer(Extent env)
       {
           Coordinate tl = new Coordinate(env.MinX, env.MaxY);
           Coordinate br = new Coordinate(env.MaxX, env.MinY);
           Point topLeft = ProjToBuffer(tl);
           Point bottomRight = ProjToBuffer(br);
           return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
       }




       /// <summary>
       /// Calculates an integer length distance in pixels that corresponds to the double
       /// length specified in the image.
       /// 加入计算距离的函数
       /// </summary>
       /// <param name="this">The IProj that this describes</param>
       /// <param name="distance">The double distance to obtain in pixels</param>
       /// <returns>The integer distance in pixels</returns>
       public double ProjToBuffer(double distance)
       {

           IEnvelope view = ViewExtents.ToEnvelope();

           return (distance * (_width / view.Width));

      


       }

       /// <summary>
       /// 计算像素距离，对应的实际距离
       /// </summary>
       /// <param name="distanceInPixel"></param>
       /// <returns></returns>
       public double BufferToProj(double distanceInPixel)
       {

           IEnvelope view = ViewExtents.ToEnvelope();

           return distanceInPixel * view.Width / _width;

 
       }



     




       /// <summary>
       /// Obtains a rectangle relative to the background image by comparing
       /// the current View rectangle with the parent control's size.
       /// </summary>
       /// <param name="clip"></param>
       /// <returns></returns>
       public Rectangle ParentToView(Rectangle clip)
       {
           Rectangle result = new Rectangle
           {
               X = View.X + (clip.X * View.Width) / _parent.ClientRectangle.Width,
               Y = View.Y + (clip.Y * View.Height) / _parent.ClientRectangle.Height,
               Width = clip.Width * View.Width / _parent.ClientRectangle.Width,
               Height = clip.Height * View.Height / _parent.ClientRectangle.Height
           };
           return result;
       }




       public void SuspendEvents()
       {
           this._isSuspend = true;

 
       }


       public void ResumeEvents()
       {

           this._isSuspend = false;

           this.Parent.Refresh();
          

 
       }




       #region 流程控制


       /// <summary>
       /// Draws from the buffer.
       /// </summary>
       /// <param name="pe"></param>
       public void Draw(PaintEventArgs pe)
       {
           if (_buffer == null) return;

           Rectangle clip = pe.ClipRectangle;
           if (clip.IsEmpty) clip = _parent.ClientRectangle;
           Rectangle clipView = ParentToView(clip);
           if (clip.Width == 0 || clip.Height == 0) return;
           if (clipView.Width == 0 || clipView.Height == 0) return;
           try
           {
               pe.Graphics.DrawImage(_buffer, clip, clipView, GraphicsUnit.Pixel);
           }
           catch
           {
               // There was an exception (probably because of sizing issues) so don't bother with the chunk timer.
               return;
           }

           //base.OnPaint(pe);
       }




       /// <summary>
       /// Uses the current buffer and envelope to force each of the contained layers
       /// to re-draw their content.  This is useful after a zoom or size change.
       /// 强制各层数据重绘自身，一般有放大，缩小，图层变化等引发
       /// </summary>
       public virtual void Initialize()
       {
           if (ClientRectangle.Width == 0 || ClientRectangle.Height == 0) return;
           Initialize(new List<Extent> { ViewExtents });
       }




       /// <summary>
       /// Instructs the map frame to draw content from the specified regions to the buffer..
       /// </summary>
       /// <param name="regions">The regions to initialize.</param>
       public virtual void Initialize(List<Extent> regions)
       {
           //保证不是空的

           if (_backBuffer == null)
           {
               _backBuffer = CreateBuffer();
           }



           //默认执行高端绘制
           Graphics bufferDevice = Graphics.FromImage(_backBuffer);

           bufferDevice.CompositingQuality = CompositingQuality.HighQuality;
           bufferDevice.CompositingMode = CompositingMode.SourceOver;
           bufferDevice.SmoothingMode = SmoothingMode.HighQuality;


          


           FMapArgs args = new FMapArgs(ClientRectangle, ViewExtents, bufferDevice);

           GraphicsPath gp = new GraphicsPath();
           foreach (Extent region in regions)
           {
               if (region == null) continue;
               Rectangle rect = args.ProjToPixel(region);
               gp.StartFigure();
               gp.AddRectangle(rect);
           }


           bufferDevice.Clip = new Region(gp);

           // Draw the background color
           if (null != _parent) bufferDevice.Clear(_parent.BackColor);
           else bufferDevice.Clear(Color.White);

           // First draw all the vector content

           foreach (FLayer layer in this._layerService)
           {
               if (layer.VisibleAtExtent(ViewExtents)) layer.DrawRegions(args, regions);
           }
          

          

           if (_buffer != null && _buffer != _backBuffer) _buffer.Dispose();
           _buffer = _backBuffer;
           _view = _backView;
           bufferDevice.Clip = new Region(this.ClientRectangle);
           gp.Dispose();


           List<Rectangle> rects = args.ProjToPixel(regions);
           OnBufferChanged(this, new FClipArgs(rects));

       }


       #endregion


       #region 选择系统

       public void Select(Extent selectedArea, FSelectionMode smode, FSelectionOperationMode omode)
       {

           
           //先写默认的全局选择机制，总的来说还是比较方便的，但是很容易选错东西，造成多选


           this._layerService.Select(selectedArea,smode,omode);


 

       }



       #endregion


       /// <summary>
       /// Fires the BufferChanged event.  This is fired even if the new content is not currently
       /// in the view rectangle.
       /// 非常重要的事件，要求控件刷新
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       protected void OnBufferChanged(object sender, FClipArgs e)
       {
           if (BufferChanged != null) BufferChanged(this, e);
       }










       /// <summary>
       /// 给出一个通用的接口
       /// </summary>
       public void RedrawBuffer()
       {

           //首先询问是否允许

           if (!this._isSuspend)
           {

               this._parent.Refresh();




           }
 
       }


   

    }
}
